/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package digitrencer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.Iterator;
/**
 * A kompozit elemet megvalósító osztály.
 * @author taplonaplo
 */
public class CompositeElement extends Element {
    /**
     * A vezetékek listája.
     */
    protected ArrayList<Wire> wireList = new ArrayList<Wire>();
    /**
     * Az alkatrészek (kapuk, ki- és bemenetek, kompozit elemek) listája.
     */
    protected ArrayList<Element> elementList = new ArrayList<Element>();
    /**
     * A kompozit elemek listája.
     */
    protected HashMap<Integer, CompositeElement> compositeList = new HashMap<Integer, CompositeElement>();

    /**
     * A küszöbérték, ahányszor vizsgálunk egy hálózatot mielött instabilnak
     * nyílvánítjuk.
     */
    protected static final int stabilityLimit = 1000;

    private HashMap<Integer, Tag> tagList = new HashMap<Integer, Tag>();


    /**
     * Nincs felelőssége
     * @param inputs
     * @param outputs
     */
    public CompositeElement(Wire[] inputs, Wire[] outputs){
            super(inputs,outputs);
    }

    /**
     * Metódus új elem hozzáadásához.
     * @param type Az elem típusa
     * @param inputs Az elem bementi vezetékei
     * @param outputs Az elem kimeneti vezetékei
     * @return Az újonnan hozzáadott elem azonosítója
     * @throws IllegalArgumentException Ha szabálytalan elemet próbálunk létrehozni
     */
    public int addElement(String type, int[] inputs, int[] outputs) throws IllegalArgumentException {
        //Vezetékek tömbbe rendezése ID alapján
        Wire[] temp_in = new Wire[inputs.length];
        Wire[] temp_out = new Wire[outputs.length];
        int j = 0;
        for (int i : inputs)
            temp_in[j++] = wireList.get(i);
        j = 0;
        for (int i : outputs)
            temp_out[j++] = wireList.get(i);
        //Elem hozzáadása
        int id = elementList.size();
        try {
            if (type.compareTo("and") == 0){
                And_gate newelem = new And_gate(temp_in,temp_out);
                elementList.add(newelem);
            }
            else if(type.compareTo("or") == 0){
                Or_gate newelem = new Or_gate(temp_in,temp_out);
                elementList.add(newelem);
            }
            else if(type.compareTo("inverter") == 0){
                Inverter newelem = new Inverter(temp_in,temp_out);
                elementList.add(newelem);
            }
            else if(type.compareTo("tag") == 0){
                Tag newelem = new Tag(temp_in,temp_out);
                elementList.add(newelem);
                tagList.put(id, newelem);
            }
            else throw new IllegalArgumentException("Unknown element: " + type);
        }
        catch (IllegalArgumentException e){
            throw e;
        }

        return id;
    }

    /**
     * Kompozit elem hozzádása. Ezzel adhatunk hozzá egy kompozit elemet egy
     * másik kompozit elemhez.
     * @param ce A hozzáadandó kompozit elem
     * @param inputs A kompozit elemhez csatlakozó bemeneti vezetékek
     * @param outputs A kompozit elemhez csatlakozó kimeneti vezetékek
     * @return A hozzáadott elem azonosítója
     * @throws IllegalArgumentException Ha helytelen a lábak bedrótozása
     */
    public int addComopsiteElement(CompositeElement ce, int[][] inputs, int[][] outputs) throws IllegalArgumentException {
        int id = elementList.size();
        elementList.add(ce);
        compositeList.put(id,ce);
        //Hashmapek legyártása
        HashMap<Integer,Wire> temp_in = new HashMap<Integer, Wire>();
        HashMap<Integer,Wire> temp_out = new HashMap<Integer, Wire>();
        for (int[] i : inputs){
            if (temp_in.put(new Integer(i[0]), wireList.get(i[1])) != null) {
                throw new IllegalArgumentException("There is more than one wire attached to pin " + i[0] + ".");
            }
        }
        for (int[] o : outputs){
            if (temp_out.put(new Integer(o[0]), wireList.get(o[1])) != null) {
                throw new IllegalArgumentException("There is more than one wire attached to pin " + o[0] + ".");
            }
        }
        //Lábak bedrotozása
        ce.setTags(temp_in, temp_out);
        return id;
    }

    /**
     * Vezeték hozzáadása kompozit elemhez.
     * @return A betett vezeték azonosítója
     */
    public int addWire() {
        int id = wireList.size();
        wireList.add(new Wire());
        return id;
    }

    /**
     * Kompozit elem alaphelyzetbe álíítása. Alaphelyzetben a minden vezeték
     * értéke 0.
     */
    public void reset() {
        //Vezetékek resetje (nulláz majd frissit)
        for(Wire w : wireList){
            w.setStart(false);
            w.refresh();
        }
        //A benne lévő kompozit elemek resetelése
        Collection<CompositeElement> c2 = compositeList.values();
        Iterator<CompositeElement> iter2 = c2.iterator();
        while (iter2.hasNext()){
            iter2.next().reset();
        }
    }

    /**
     * Kompozit elem futási metódusa. Az elemeket és vezetékeket felváltva
     * fríssíti, amíg a hálózat nem stabilizálódik, vagy a probálkozások száma
     * túllépi a küszöbértéket.
     * @throws UnstableNetworkException Ha instabil a hálózat.
     */
    @Override
    public void run() throws UnstableNetworkException {
        boolean isStable = false;
        //Az öszes vezetéket és elemet felváltva futtatjuk.
        for (int i = 0; i < stabilityLimit && !isStable; i++){
            for (Element e: elementList){
                e.run();
            }
            isStable = true;
            for (Wire w: wireList){
                if (w.refresh())
                    isStable = false;
            }
        }
        if (!isStable)
            throw new UnstableNetworkException("Unstable network.");
    }

    /**
     * Tag lábának sorszámának beállítására szolgáló metódus.
     * @param ID A Tag amit módosítani akarunk
     * @param pin A láb sorszáma
     * @throws IllegalArgumentException Ha nem Tag az adott IDjű elem, vagy már beállítottuk a pin értékét
     */
    public void setPin(int ID, int pin) throws IllegalArgumentException {
            if (tagList.get(ID) != null)
                tagList.get(ID).setPin(pin);
            else
                throw new IllegalArgumentException("Element with ID "+ Integer.toString(ID) + " is not a tag.");
        }

    /**
     * Ez a metódus állítja be a kompozit elem tényleges ki és bemeneteit. Mivel
     * a kompozit elemek a külső hálózattal a Tagjeiken keresztül kommunikálnak,
     * ezért azokba kell beállítani a kinti vezetékeket.
     * @param inputs A kompozit elem bemeneti oldalára csatlakoztatott vezetékek
     * @param outputs A kompozit elem kimeneti oldalára csatlakoztatott vezetékek
     * @throws IllegalArgumentException Ha a Tag sorszáma hibás, vagy már be van állítva a vezeték
     */
    protected void setTags(HashMap<Integer, Wire> inputs, HashMap<Integer, Wire> outputs) throws IllegalArgumentException{
            Collection<Tag> c = tagList.values();
            Iterator<Tag> iter = c.iterator();
            Tag temp;
            while (iter.hasNext()){
                temp = iter.next();
                if (inputs.get(temp.getPin()) != null)
                    temp.setInput(inputs.get(temp.getPin()));
                else if (outputs.get(temp.getPin()) != null)
                    temp.setOutput(outputs.get(temp.getPin()));
                else throw new IllegalArgumentException("Missing wire for Pin #" + Integer.toString(temp.getPin())+ ".");
            }
            if (inputs.size() + outputs.size() != tagList.size()) {
                throw new IllegalArgumentException("Wire attached to non-existent pin");
            }
        }
}
